package com.dliyun.oap.framework.context;

import com.dliyun.oap.framework.SystemParameterNames;
import com.dliyun.oap.framework.annotation.*;
import com.dliyun.oap.framework.exception.OapException;
import com.dliyun.oap.framework.interceptor.OapInterceptor;
import com.dliyun.oap.framework.request.AbstractOapRequest;
import com.dliyun.oap.framework.request.OapRequest;
import com.dliyun.oap.framework.request.UploadFile;
import com.dliyun.oap.framework.response.OapResponse;
import com.dliyun.oap.framework.service.ServiceMethodHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * <pre>
 * OAP框架的上下文
 * </pre>
 *
 * @version 1.0
 */
@Slf4j
public class OapContext {

    private final Map<String, ServiceMethodHandler> SERVICE_HANDLER_MAP = new HashMap<>();

    private final Set<String> SERVICE_METHODS = new HashSet<>();

    private final List<OapInterceptor> interceptors = new ArrayList<>();

    public void addServiceMethod(String methodName, String version, ServiceMethodHandler serviceMethodHandler) {
        SERVICE_METHODS.add(methodName);
        SERVICE_HANDLER_MAP.put(ServiceMethodHandler.methodWithVersion(methodName, version), serviceMethodHandler);
    }

    public ServiceMethodHandler getServiceMethodHandler(String methodName, String version) {
        return SERVICE_HANDLER_MAP.get(ServiceMethodHandler.methodWithVersion(methodName, version));
    }

    public boolean isValidMethod(String methodName) {
        return !SERVICE_METHODS.contains(methodName);
    }

    public boolean isValidVersion(String methodName, String version) {
        return SERVICE_HANDLER_MAP.containsKey(ServiceMethodHandler.methodWithVersion(methodName, version));
    }

    public Map<String, ServiceMethodHandler> getAllServiceMethodHandlers() {
        return SERVICE_HANDLER_MAP;
    }

    public void registerServiceHandler(ApplicationContext context) throws BeansException {
        if (log.isDebugEnabled()) {
            log.debug("对Spring上下文中的Bean进行扫描，查找OAP服务方法: " + context);
        }
        String[] beanNames = context.getBeanNamesForType(Object.class);
        for (final String beanName : beanNames) {
            Class<?> handlerType = context.getType(beanName);
            if (handlerType == null) {
                continue;
            }
            // 只对标注 ServiceMethodBean的Bean进行扫描
            if (AnnotationUtils.findAnnotation(handlerType, ServiceMethodBean.class) != null) {
                ReflectionUtils.doWithMethods(handlerType, method -> {
                    ReflectionUtils.makeAccessible(method);

                    ServiceMethod serviceMethod = AnnotationUtils.findAnnotation(method, ServiceMethod.class);
                    ServiceMethodBean serviceMethodBean = AnnotationUtils.findAnnotation(method.getDeclaringClass(), ServiceMethodBean.class);

                    if (serviceMethod != null) {
                        ServiceMethodDefinition definition;
                        if (serviceMethodBean != null) {
                            definition = buildServiceMethodDefinition(serviceMethodBean, serviceMethod);
                        } else {
                            definition = buildServiceMethodDefinition(serviceMethod);
                        }
                        ServiceMethodHandler serviceMethodHandler = new ServiceMethodHandler();
                        serviceMethodHandler.setServiceMethodDefinition(definition);

                        // 1.set handler
                        serviceMethodHandler.setHandler(context.getBean(beanName)); // handler
                        serviceMethodHandler.setHandlerMethod(method); //handler'method
                        Class<?> returnType = method.getReturnType();
                        // 验证返回参数必须是OapResponse.class
                        if (!ClassUtils.isAssignable(OapResponse.class, returnType)) {
                            throw new OapException(method.getDeclaringClass().getName() + "." + method.getName() + "的返回必须是" + OapResponse.class.getName());
                        }

                        // handler method's parameter
                        if (method.getParameterTypes().length > 1) {
                            throw new OapException(method.getDeclaringClass().getName() + "." + method.getName() + "的入参只能是" + OapRequest.class.getName() + "或无入参。");
                        } else if (method.getParameterTypes().length == 1) {
                            Class<?> paramType = method.getParameterTypes()[0];
                            if (!ClassUtils.isAssignable(OapRequest.class, paramType)) {
                                throw new OapException(method.getDeclaringClass().getName() + "." + method.getName() + "的入参必须是" + OapRequest.class.getName());
                            }
                            boolean oapRequestImplType = !(paramType.isAssignableFrom(OapRequest.class) || paramType.isAssignableFrom(AbstractOapRequest.class));
                            serviceMethodHandler.setRequestImplType(oapRequestImplType);
                            serviceMethodHandler.setRequestType((Class<? extends OapRequest>) paramType);
                        } else {
                            log.info(method.getDeclaringClass().getName() + "." + method.getName() + "无入参");
                        }

                        // 2.set fileItemFieldNames
                        serviceMethodHandler.setUploadFileFieldNames(getFileItemFieldNames(serviceMethodHandler.getRequestType()));

                        addServiceMethod(definition.getMethod(), definition.getVersion(), serviceMethodHandler);
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("注册服务方法：" + method.getDeclaringClass().getCanonicalName() + "#" + method.getName() + "(..)");
                    }
                }, method -> !method.isSynthetic() && AnnotationUtils.findAnnotation(method, ServiceMethod.class) != null);

            }
        }
        if (context.getParent() != null) {
            registerServiceHandler(context.getParent());
        }
        if (log.isInfoEnabled()) {
            log.info("共注册了" + SERVICE_HANDLER_MAP.size() + "个服务方法");
        }
    }

    public void registerInterceptors(ApplicationContext context) {

        Map<String, OapInterceptor> interceptorMap = context.getBeansOfType(OapInterceptor.class);

        log.info("开始注册拦截器");
        if (interceptorMap.size() > 0) {
            //从Spring容器中获取Interceptor
            this.interceptors.addAll(interceptorMap.values());
            //根据getOrder()值排序
            this.interceptors.sort(Comparator.comparingInt(OapInterceptor::getOrder));
        }
        log.info("共注册了{}个拦截器", this.interceptors.size());
    }

    public List<OapInterceptor> getInterceptors() {
        return this.interceptors;
    }

    private ServiceMethodDefinition buildServiceMethodDefinition(ServiceMethod serviceMethod) {
        ServiceMethodDefinition definition = new ServiceMethodDefinition();
        definition.setMethod(serviceMethod.method());
        definition.setMethodTitle(serviceMethod.title());
        definition.setMethodGroup(serviceMethod.group());
        definition.setMethodGroupTitle(serviceMethod.groupTitle());
        definition.setTags(serviceMethod.tags());
        definition.setTimeout(serviceMethod.timeout());
        definition.setVersion(serviceMethod.version());
        definition.setObsoleted(ObsoletedType.isObsoleted(serviceMethod.obsoleted()));
        definition.setHttpAction(serviceMethod.httpAction());
        return definition;
    }

    private ServiceMethodDefinition buildServiceMethodDefinition(ServiceMethodBean serviceMethodBean, ServiceMethod serviceMethod) {
        ServiceMethodDefinition definition = new ServiceMethodDefinition();
        definition.setMethodGroup(serviceMethodBean.group());
        definition.setMethodGroupTitle(serviceMethodBean.groupTitle());
        definition.setTags(serviceMethodBean.tags());
        definition.setTimeout(serviceMethodBean.timeout());
        definition.setVersion(serviceMethodBean.version());
        definition.setHttpAction(serviceMethodBean.httpAction());
        definition.setObsoleted(ObsoletedType.isObsoleted(serviceMethodBean.obsoleted()));

        // 如果ServiceMethod所提供的值和ServiceMethodGroup不一样，覆盖之
        definition.setMethod(serviceMethod.method());
        definition.setMethodTitle(serviceMethod.title());

        if (!ServiceMethodDefinition.DEFAULT_GROUP.equals(serviceMethod.group())) {
            definition.setMethodGroup(serviceMethod.group());
        }

        if (!ServiceMethodDefinition.DEFAULT_GROUP_TITLE.equals(serviceMethod.groupTitle())) {
            definition.setMethodGroupTitle(serviceMethod.groupTitle());
        }

        if (serviceMethod.tags().length > 0) {
            definition.setTags(serviceMethod.tags());
        }

        if (serviceMethod.timeout() > 0) {
            definition.setTimeout(serviceMethod.timeout());
        }

        if (StringUtils.hasText(serviceMethod.version())) {
            definition.setVersion(serviceMethod.version());
        }

        if (serviceMethod.obsoleted() != ObsoletedType.DEFAULT) {
            definition.setObsoleted(ObsoletedType.isObsoleted(serviceMethod.obsoleted()));
        }

        if (serviceMethod.httpAction().length > 0) {
            definition.setHttpAction(serviceMethod.httpAction());
        }

        return definition;
    }

    private List<String> getFileItemFieldNames(Class<? extends OapRequest> requestType) {
        final ArrayList<String> fileItemFieldNames = new ArrayList<>(1);
        if (requestType != null) {
            if (log.isDebugEnabled()) {
                log.debug("获取" + requestType.getCanonicalName() + "类型为FileItem的字段名");
            }

            ReflectionUtils.doWithFields(requestType, field -> fileItemFieldNames.add(field.getName()), field -> ClassUtils.isAssignable(UploadFile.class, field.getType()));
        }
        return fileItemFieldNames;
    }
}
